package org.desktop.intbox.components.treemap;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

import org.desktop.intbox.components.treemap.actions.BorderColorAction;
import org.desktop.intbox.components.treemap.actions.FillColorAction;
import org.desktop.intbox.components.treemap.layouts.LabelLayout;
import org.desktop.intbox.components.treemap.layouts.ScalingTreeLayout;
import org.desktop.intbox.components.treemap.renderers.NodeRenderer;
import org.desktop.intbox.components.treemap.renderers.TreeMapLabelRenderer;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.SizeAction;
import prefuse.action.layout.graph.SquarifiedTreeMapLayout;
import prefuse.controls.ControlAdapter;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Schema;
import prefuse.data.Tree;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.query.SearchQueryBinding;
import prefuse.render.DefaultRendererFactory;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.PrefuseLib;
import prefuse.util.UpdateListener;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.ui.UILib;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTree;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;

/**
 * Demonstration showcasing a TreeMap layout of a hierarchical data set and the
 * use of dynamic query binding for text search. Animation is used to highlight
 * changing search results.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class TreeMapDisplay extends Display {

	/**
	 * Default serial version UID.
	 */
	private static final long serialVersionUID = 1L;

	// create data description of labels, setting colors, fonts ahead of time
	private static final Schema LABEL_SCHEMA = PrefuseLib.getVisualItemSchema();
	static {
		LABEL_SCHEMA.setDefault(VisualItem.INTERACTIVE, false);
		LABEL_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(200));
		LABEL_SCHEMA.setDefault(VisualItem.FONT, FontLib.getFont("Tahoma", 16));
	}

	private static final String LAYOUT_ACTION_LIST_NAME = "layout"; // NOI18N
	private static final String ANIMATE_PAINT_ACTION_LIST_NAME = "animatePaint"; // NOI18N
	private static final String COLORS_LIST_NAME = "colors"; // NOI18N

	private static final String TREE_LAYOUT_ACTION_NAME = "treeLayout";

	// Names of groups & their attributes
	private static final String TREE = "tree"; // NOI18N
	private static final String TREE_NODES = "tree.nodes"; // NOI18N
	private static final String TREE_EDGES = "tree.edges"; // NOI18N
	private static final String LABELS = "labels";

	private SearchQueryBinding searchQ;

	public TreeMapDisplay(final Tree t) {
		super(new Visualization());

		// add the tree to the visualization
		VisualTree visualTree = m_vis.addTree(TREE, t);
		m_vis.setVisible(TREE_EDGES, null, false);

		// ensure that only leaf nodes are interactive
		Predicate noLeaf = (Predicate) ExpressionParser.parse("childcount()>0");
		m_vis.setInteractive(TREE_NODES, noLeaf, false);

		// add labels to the visualization
		// first create a filter to show labels only at top-level nodes
		Predicate labelP = (Predicate) ExpressionParser.parse("treedepth()=1");
		// now create the labels as decorators of the nodes
		m_vis.addDecorators(LABELS, TREE_NODES, labelP, LABEL_SCHEMA);

		// set up the renderers - one for nodes and one for labels
		DefaultRendererFactory rf = new DefaultRendererFactory();
		rf.add(new InGroupPredicate(TREE_NODES), new NodeRenderer());
		rf.add(new InGroupPredicate(LABELS), new TreeMapLabelRenderer());
		m_vis.setRendererFactory(rf);

		// border colors
		final ColorAction borderColor = new BorderColorAction(TREE_NODES);
		final ColorAction fillColor = new FillColorAction(TREE_NODES);

		// color settings
		ActionList colors = new ActionList();
		colors.add(fillColor);
		colors.add(borderColor);
		m_vis.putAction(COLORS_LIST_NAME, colors);

		// animate paint change
		ActionList animatePaint = new ActionList(400);
		animatePaint.add(new ColorAnimator(TREE_NODES));
		animatePaint.add(new RepaintAction());
		m_vis.putAction(ANIMATE_PAINT_ACTION_LIST_NAME, animatePaint);

		// create the single filtering and layout action list
		ActionList layout = new ActionList();
		layout.add(new SizeAction(TREE) {
			public double getSize(VisualItem item) {
				return ((Number) item.get(TreeBuilder.AREA_ATTR)).doubleValue();
			};
		});
		layout.add(new SquarifiedTreeMapLayout(TREE));
		layout.add(new LabelLayout(LABELS));
		layout.add(colors);
		layout.add(new RepaintAction());
		m_vis.putAction(LAYOUT_ACTION_LIST_NAME, layout);

		// create the tree layout action
		final ScalingTreeLayout treeLayout = new ScalingTreeLayout(TREE);

		// treeLayout.setAngularBounds(-Math.PI/2, Math.PI);
		m_vis.putAction(TREE_LAYOUT_ACTION_NAME, treeLayout);

		// initialize our display
		// setSize(700, 600);
		setItemSorter(new TreeDepthItemSorter());
		addControlListener(new ControlAdapter() {
			public void itemEntered(VisualItem item, MouseEvent e) {
				item.setStrokeColor(borderColor.getColor(item));
				item.getVisualization().repaint();
			}

			public void itemExited(VisualItem item, MouseEvent e) {
				item.setStrokeColor(item.getEndStrokeColor());
				item.getVisualization().repaint();
			}
		});

		searchQ = new SearchQueryBinding(visualTree.getNodeTable(), TreeBuilder.LABEL_ATTR);
		m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchQ.getSearchSet());
		searchQ.getPredicate().addExpressionListener(new UpdateListener() {
			public void update(Object src) {
				m_vis.cancel(ANIMATE_PAINT_ACTION_LIST_NAME);
				m_vis.run(COLORS_LIST_NAME);
				m_vis.run(ANIMATE_PAINT_ACTION_LIST_NAME);
			}
		});

		// Rescale the display when parent component is rescaled
		UpdateListener updateListener = new UpdateListener() {
			public void update(Object src) {
			}

			public void componentResized(java.awt.event.ComponentEvent e) {
				double x = e.getComponent().getWidth();
				double y = e.getComponent().getHeight();
				treeLayout.setNewSize(x, y);
				m_vis.run(LAYOUT_ACTION_LIST_NAME);
			}
		};

		addComponentListener(updateListener);

		// perform layout
		m_vis.run(LAYOUT_ACTION_LIST_NAME);
	}

	public SearchQueryBinding getSearchQuery() {
		return searchQ;
	}

	public static JComponent createDemoComponent(final Tree t) {
		// create a new treemap
		final TreeMapDisplay treemap = new TreeMapDisplay(t);

		// create a search panel for the tree map
		JSearchPanel search = treemap.getSearchQuery().createSearchPanel();
		search.setShowResultCount(true);
		search.setBorder(BorderFactory.createEmptyBorder(5, 5, 4, 0));
		search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));

		final JFastLabel title = new JFastLabel("                 ");
		title.setPreferredSize(new Dimension(350, 20));
		title.setVerticalAlignment(SwingConstants.BOTTOM);
		title.setBorder(BorderFactory.createEmptyBorder(3, 0, 0, 0));
		title.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 16));

		treemap.addControlListener(new ControlAdapter() {
			public void itemEntered(VisualItem item, MouseEvent e) {
				title.setText(item.getString(TreeBuilder.LABEL_ATTR));
			}

			public void itemExited(VisualItem item, MouseEvent e) {
				title.setText(null);
			}
		});
		treemap.addControlListener(new DragControl());
		treemap.addControlListener(new PanControl());
		treemap.addControlListener(new ZoomToFitControl());
		treemap.addControlListener(new ZoomControl());

		Box box = UILib.getBox(new Component[] { title, search }, true, 10, 3, 0);

		JPanel panel = new JPanel(new BorderLayout());
		panel.add(treemap, BorderLayout.CENTER);
		panel.add(box, BorderLayout.SOUTH);
		UILib.setColor(panel, Color.BLACK, Color.GRAY);
		return panel;
	}
}
